#include <unistd.h>
#include <signal.h>

#include <libubus.h>

static struct ubus_context *ctx;
static struct blob_buf b;

static char device_field_data[32] = {"9.0.0"};

enum
{
    DEVICE_NAME,
    DEVICE_FIELD,
    DEVICE_DATA,
    __DEVICE_MAX
};

static const struct blobmsg_policy device_read_policy[] = {
    [DEVICE_NAME] = {.name = "devname", .type = BLOBMSG_TYPE_STRING},
    [DEVICE_FIELD] = {.name = "fieldname", .type = BLOBMSG_TYPE_STRING},
};

static const struct blobmsg_policy device_write_policy[] = {
    [DEVICE_NAME] = {.name = "devname", .type = BLOBMSG_TYPE_STRING},
    [DEVICE_FIELD] = {.name = "fieldname", .type = BLOBMSG_TYPE_STRING},
    [DEVICE_DATA] = {.name = "data", .type = BLOBMSG_TYPE_STRING},
};

static int device_read(struct ubus_context *ctx, struct ubus_object *obj,
                       struct ubus_request_data *req, const char *method,
                       struct blob_attr *msg)
{
    struct blob_attr *tb[__DEVICE_MAX];
    char *devname;
    char *fieldname;

    blobmsg_parse(device_read_policy, 2, tb, blob_data(msg), blob_len(msg));
    if (!tb[DEVICE_NAME] || !tb[DEVICE_FIELD])
        return UBUS_STATUS_INVALID_ARGUMENT;

    devname = blobmsg_get_string(tb[DEVICE_NAME]);
    fieldname = blobmsg_get_string(tb[DEVICE_FIELD]);
    if (!devname || !fieldname)
        return UBUS_STATUS_UNKNOWN_ERROR;

    fprintf(stderr, "get device '%s' field '%s' with data %s\n", devname, fieldname, device_field_data);

    blob_buf_init(&b, 0);
    blobmsg_add_u32(&b, "rc", 0);
    blobmsg_add_string(&b, "data", device_field_data);
    ubus_send_reply(ctx, req, b.head);

    return 0;
}

static int device_write(struct ubus_context *ctx, struct ubus_object *obj,
                        struct ubus_request_data *req, const char *method,
                        struct blob_attr *msg)
{
    struct blob_attr *tb[__DEVICE_MAX];
    char *devname;
    char *fieldname;
    char *data;

    blobmsg_parse(device_write_policy, 3, tb, blob_data(msg), blob_len(msg));
    if (!tb[DEVICE_NAME] || !tb[DEVICE_FIELD] || !tb[DEVICE_DATA])
        return UBUS_STATUS_INVALID_ARGUMENT;

    devname = blobmsg_get_string(tb[DEVICE_NAME]);
    fieldname = blobmsg_get_string(tb[DEVICE_FIELD]);
    data = blobmsg_get_string(tb[DEVICE_DATA]);
    if (!devname || !fieldname || !data)
        return UBUS_STATUS_UNKNOWN_ERROR;

    strncpy(device_field_data, data, 31);

    fprintf(stderr, "set device '%s' field '%s' with data %s\n", devname, fieldname, data);

    blob_buf_init(&b, 0);
    blobmsg_add_u32(&b, "rc", 0);
    ubus_send_reply(ctx, req, b.head);

    return 0;
}

static const struct ubus_method device_methods[] = {
    UBUS_METHOD("read", device_read, device_read_policy),
    UBUS_METHOD("write", device_write, device_write_policy),
};

static struct ubus_object_type device_object_type =
    UBUS_OBJECT_TYPE("device", device_methods);

static struct ubus_object device_object = {
    .name = "device",
    .type = &device_object_type,
    .methods = device_methods,
    .n_methods = ARRAY_SIZE(device_methods),
};

static void subscribe_service_objects(void)
{
    int ret;

    ret = ubus_add_object(ctx, &device_object);
    if (ret)
        fprintf(stderr, "Failed to add device object: %s\n", ubus_strerror(ret));
}

static void device_event_object_subscribe_cb(struct ubus_context *ctx, struct ubus_object *obj)
{
    fprintf(stderr, "%s: subscribers active: %d\n", __func__, obj->has_subscribers);
}

static struct ubus_method device_event_methods[] = {};

static struct ubus_object_type device_event_obj_type =
    UBUS_OBJECT_TYPE("event", device_event_methods);

static struct ubus_object device_event_object = {
    .name = "event",
    .type = &device_event_obj_type,
    .subscribe_cb = device_event_object_subscribe_cb,
};

static void notify_timer_cb(struct uloop_timeout *timeout)
{
    /* send event notifications */
    fprintf(stderr, "send event notifications\n");
    ubus_notify(ctx, &device_event_object, "hello, i am a event", NULL, -1);

    uloop_timeout_set(timeout, 1000);
}

static struct uloop_timeout notify_timer = {
    .cb = notify_timer_cb,
};

static void subscribe_service_events(void)
{
    int ret;

    ret = ubus_add_object(ctx, &device_event_object);
    if (ret)
    {
        fprintf(stderr, "Failed to add event object: %s\n", ubus_strerror(ret));
    }
    else
    {
        /* start timer to send evnet notification, just a demo */
        uloop_timeout_set(&notify_timer, 1000);
    }
}

int main(int argc, char **argv)
{
    const char *ubus_socket = NULL;

    uloop_init();
    signal(SIGPIPE, SIG_IGN);

    ctx = ubus_connect(ubus_socket);
    if (!ctx)
    {
        fprintf(stderr, "Failed to connect to ubus\n");
        return -1;
    }

    ubus_add_uloop(ctx);

    subscribe_service_objects();

    subscribe_service_events();

    uloop_run();
    ubus_free(ctx);
    uloop_done();

    return 0;
}
